﻿using System;
using System.Linq;
using System.Text;
using TSharp.Core.Util;
using TSharp.Core.Util.Security;
using TSharp.Core.Authentication.Facade;
using TSharp.Core.Authentication.Facade.Constant;

namespace TSharp.Core.Authentication.Impl
{
    /// <summary>
    /// 
    /// <para>2010/9/16</para>
    /// 	<para>THINKPADT61</para>
    /// 	<para>tangjingbo</para>
    /// </summary>
    public class TokenService : ITokenService
    {
        #region Token生成和验证

        /// <summary>
        /// Gets the URL token.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="startTime">The start time.</param>
        /// <param name="endTime">The end time.</param>
        /// <returns></returns>
        public ParameterizedUri GetTokenUrl(ParameterizedUri url, DateTime startTime, DateTime endTime)
        {
            if (startTime.Kind != DateTimeKind.Utc)
                startTime = DateTime.SpecifyKind(startTime, DateTimeKind.Utc);

            if (endTime.Kind != DateTimeKind.Utc)
                endTime = DateTime.SpecifyKind(endTime, DateTimeKind.Utc);

            long stUtc = startTime.Ticks/TokenServiceConst.TIMESTAMP;
            long etUtc = endTime.Ticks/TokenServiceConst.TIMESTAMP;
            return GetTokenUrl(url, stUtc, etUtc);
        }

        /// <summary>
        /// 检查签名的url是否合法。（不能包含用于签名的键）
        /// </summary>
        /// <param name="url">The URL.</param>
        private static void ValidUrl(ParameterizedUri url)
        {
            string[] keys = url.Queries.AllKeys;
            if (keys.Contains(TokenServiceConst.URLKEY_STARTFILETIMEUTC)
                || keys.Contains(TokenServiceConst.URLKEY_ENDFILETIMEUTC)
                || keys.Contains(TokenServiceConst.URLKEY_TOKEN))
                throw new Exception(string.Format("无效的url:{0}；不能包含键：{1},{2},{3}。",
                                                  url,
                                                  TokenServiceConst.URLKEY_TOKEN,
                                                  TokenServiceConst.URLKEY_STARTFILETIMEUTC,
                                                  TokenServiceConst.URLKEY_ENDFILETIMEUTC));
        }

        /// <summary>
        /// Gets the URL token.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="startFileTimeUtc">The start file time UTC.</param>
        /// <param name="endFileTimeUtc">The end file time UTC.</param>
        /// <returns></returns>
        public ParameterizedUri GetTokenUrl(ParameterizedUri url, long startFileTimeUtc, long endFileTimeUtc)
        {
            ValidUrl(url);
            ParameterizedUri uri = url.Clone();
            uri.SetQuery(TokenServiceConst.URLKEY_STARTFILETIMEUTC, startFileTimeUtc)
                .SetQuery(TokenServiceConst.URLKEY_ENDFILETIMEUTC, endFileTimeUtc);
            using (var util = new RSAUtil(TokenServiceConst.PrivateKeyFilePath))
            {
                string token = util.Sign(GetSignSource(uri));
                uri.SetQuery(TokenServiceConst.URLKEY_TOKEN, token);
            }
            return uri;
        }

        /// <summary>
        /// Validates the URL token.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="currentUtcTime">The current UTC time.</param>
        /// <returns></returns>
        public bool ValidateTokenUrl(ParameterizedUri url, DateTime currentUtcTime)
        {
            if (currentUtcTime.Kind != DateTimeKind.Utc)
                currentUtcTime = DateTime.SpecifyKind(currentUtcTime, DateTimeKind.Utc);
            return ValidateTokenUrl(url, currentUtcTime.Ticks/TokenServiceConst.TIMESTAMP);
        }

        /// <summary>
        /// Validates the URL token.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="currentFileTimeUtc">The current file time UTC.</param>
        /// <returns></returns>
        public bool ValidateTokenUrl(ParameterizedUri url, long currentFileTimeUtc)
        {
            var ststr = url.Queries[TokenServiceConst.URLKEY_STARTFILETIMEUTC];
            var etstr = url.Queries[TokenServiceConst.URLKEY_ENDFILETIMEUTC];
            var token = url.Queries[TokenServiceConst.URLKEY_TOKEN];
            long stUtcLong;
            if (!long.TryParse(ststr, out stUtcLong))
                return false;
            long etUtcLong;
            if (!long.TryParse(etstr, out etUtcLong))
                return false;
            if (currentFileTimeUtc > etUtcLong || currentFileTimeUtc < stUtcLong)
                return false;
            string sourceString = GetSignSource(url);
            using (var util = new RSAUtil(TokenServiceConst.PublicKeyFilePath))
                return util.Verify(sourceString, token);
        }

        private static string GetSignSource(ParameterizedUri url)
        {
            IOrderedEnumerable<string> ks =
                url.Queries.AllKeys.Where(x => x != TokenServiceConst.URLKEY_TOKEN).OrderBy(x => x);
            var all = new StringBuilder();
            foreach (string k in ks)
            {
                all.AppendFormat("{0}={1}&", k, url.Queries[k]);
            }
            if (all.Length > 1)
                all.Length = all.Length - 1;
            return all.ToString();
        }

        #endregion
    }
}